#include "stdafx.h"
#include "solitaire.h"
#include "rock.h"
#include "cell.h"

const qreal solitaire::CELL_SPACE  = 20;
const qreal solitaire::CELL_RADIUS = 15;
const qreal solitaire::ROCK_RADIUS = 10;

solitaire::solitaire(int field_size, int corner_size, QWidget *parent)
    : QGraphicsScene(parent)
    , field_size_(field_size)
    , corner_size_(corner_size)
    , cells_(field_size * field_size, NULL)
    , rocks_(field_size * field_size, NULL)
    , last_cell_(NULL)
{
    setSceneRect(0, 0, field_size_ * CELL_SPACE * 2, field_size_ * CELL_SPACE * 2);
    
    for (int y = 0; y < field_size_; ++y)
    {
        for (int x = 0; x < field_size_; ++x)
        {
            const QPoint p(x, y);
            if (check_location(p))
            {
                create_cell(p);
                if (x != field_size_ / 2 || y != field_size_ / 2)
                {
                    rock_ptr r = create_rock(p);
                    connect(r, SIGNAL(position_changed(rock_ptr, const QPointF &)), 
                        this, SLOT(drag_rock(rock_ptr, const QPointF &)));

                    connect(r, SIGNAL(dropped(rock_ptr)), 
                        this, SLOT(drop_rock(rock_ptr)));
                }
            }
        }
    }
}

solitaire::~solitaire()
{

}

cell_ptr solitaire::create_cell(const QPoint &p)
{
    cell_ptr c = new cell(p);
    addItem(c);
    c->setPos(center_coord(p));
    c->setZValue(0);
    cell_ref(p) = c;
    return c;
}

rock_ptr solitaire::create_rock(const QPoint &p)
{
    if (cell_ref(p) == NULL)
        throw std::runtime_error("Can't create rock with no cell");
    
    rock_ptr r = new rock(p);
    addItem(r);
    r->setPos(center_coord(p));
    r->setZValue(1);
    rock_ref(p) = r;
    return r;
}

void solitaire::drag_rock(rock_ptr r, const QPointF &pos)
{
    QList<QGraphicsItem*> found = items(pos, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder);
    for (auto it = found.begin(); it != found.end(); ++it)
    {
        cell *c = dynamic_cast<cell*>(*it);
        if (c == NULL)
            continue;

        highlight_cell(r, c);
    }
    //reset_last_cell();
}

void solitaire::highlight_cell(const_rock_ptr r, cell_ptr c)
{
    reset_last_cell();
    if (r->get_coord() == c->get_coord())
        return;

    last_cell_ = c;

    const QPoint cell_coord = c->get_coord();
    if (!can_move_rock(r, c, NULL))
        c->set_color(Qt::red);
    else
        c->set_color(Qt::green);
    
    c->update();
}

void solitaire::reset_last_cell()
{
    if (last_cell_ != NULL)
    {
        last_cell_->set_color(Qt::white);
        last_cell_->update();
        last_cell_ = NULL;
    }

}

bool solitaire::can_move_rock(const_rock_ptr r, const_cell_ptr c, rock_ptr *victim) const
{
    const QPoint rock_coord = r->get_coord();
    const QPoint cell_coord = c->get_coord();
    
    if (rock_ref(cell_coord) != NULL)
        return false;

    vector<QPoint> dirs;
    dirs.push_back(QPoint(1, 0));
    dirs.push_back(QPoint(-1, 0));
    dirs.push_back(QPoint(0, 1));
    dirs.push_back(QPoint(0, -1));
    
    for (auto it = dirs.begin(); it != dirs.end(); ++it)
    {
        const QPoint &dir = *it;
        if (cell_coord != rock_coord + dir * 2)
            continue;

        rock_ptr v = rock_ref(rock_coord + dir);
        if (v != NULL)
        {
            if (victim != NULL)
                *victim = v;
            return true;
        }
    }
    
    return false;
}

void solitaire::drop_rock(rock_ptr r)
{
    rock_ptr victim = NULL;
    if (last_cell_ == NULL || !can_move_rock(r, last_cell_, &victim))
    {
        r->setPos(center_coord(r->get_coord()));
        r->update();
        reset_last_cell();
        return;
    }

    const QPoint old_coord = r->get_coord();
    const QPoint new_coord = last_cell_->get_coord();
    r->set_coord(new_coord);
    rock_ref(old_coord) = NULL;
    rock_ref(new_coord) = r;
    r->setPos(center_coord(r->get_coord()));

    rock_ref(victim->get_coord()) = NULL;
    removeItem(victim);

    r->update();
    reset_last_cell();
}

